package com.genersoft.iot.vmp.gb28181.transmit.request.impl;

import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
import com.genersoft.iot.vmp.gb28181.transmit.request.SIPRequestAbstractProcessor;
import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.IPlayService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorager;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.PlayResult;
import com.genersoft.iot.vmp.vmanager.record.TcpTrans;
import gov.nist.javax.sip.address.AddressImpl;
import gov.nist.javax.sip.address.SipUri;
import gov.nist.javax.sip.header.ContentType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import javax.sdp.*;
import javax.sip.*;
import javax.sip.address.Address;
import javax.sip.address.SipURI;
import javax.sip.header.ContentTypeHeader;
import javax.sip.header.FromHeader;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Vector;

/**
 * @Description:处理INVITE请求
 * @author: panll
 * @date: 2021年1月14日
 */
@SuppressWarnings("rawtypes")
public class InviteRequestProcessor extends SIPRequestAbstractProcessor {

    private final static Logger logger = LoggerFactory.getLogger(MessageRequestProcessor.class);

    private SIPCommanderFroPlatform cmderFroPlatform;

    private IVideoManagerStorager storager;

    private IRedisCatchStorage redisCatchStorage;

    private SIPCommander cmder;

    private IPlayService playService;

    private ZLMRTPServerFactory zlmrtpServerFactory;

    private IMediaServerService mediaServerService;

    private RedisUtil redisUtil;

    public static String port1 = "";
    @Autowired
    private SipConfig sipConfig;

    public ZLMRTPServerFactory getZlmrtpServerFactory() {
        return zlmrtpServerFactory;
    }

    public void setZlmrtpServerFactory(ZLMRTPServerFactory zlmrtpServerFactory) {
        this.zlmrtpServerFactory = zlmrtpServerFactory;
    }

    /**
     * 处理invite请求
     *
     * @param evt 请求消息
     */
    @Override
    public void process(RequestEvent evt) {
        //  Invite Request消息实现，此消息一般为级联消息，上级给下级发送请求视频指令
        try {
            Request request = evt.getRequest();
            SipURI sipURI = (SipURI) request.getRequestURI();
            String channelId = sipURI.getUser();
            String requesterId = null;

            FromHeader fromHeader = (FromHeader) request.getHeader(FromHeader.NAME);
            AddressImpl address = (AddressImpl) fromHeader.getAddress();
            SipUri uri = (SipUri) address.getURI();
            requesterId = uri.getUser();

            if (requesterId == null || channelId == null) {
                logger.info("无法从FromHeader的Address中获取到平台id，返回400");
                responseAck(evt, Response.BAD_REQUEST); // 参数不全， 发400，请求错误
                return;
            }

            // 查询请求方是否上级平台
            ParentPlatform platform = storager.queryParentPlatByServerGBId(requesterId);
            if (platform != null) {
                // 查询平台下是否有该通道
                DeviceChannel channel = storager.queryChannelInParentPlatform(requesterId, channelId);
                GbStream gbStream = storager.queryStreamInParentPlatform(requesterId, channelId);
                MediaServerItem mediaServerItem = null;
                // 不是通道可能是直播流
                if (channel != null && gbStream == null) {
                    if (channel.getStatus() == 0) {
                        logger.info("通道离线，返回400");
                        responseAck(evt, Response.BAD_REQUEST, "channel [" + channel.getChannelId() + "] offline");
                        return;
                    }
                    responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在，发181，呼叫转接中
                } else if (channel == null && gbStream != null) {
                    String mediaServerId = gbStream.getMediaServerId();
                    mediaServerItem = mediaServerService.getOne(mediaServerId);
                    if (mediaServerItem == null) {
                        logger.info("[ app={}, stream={} ]zlm找不到，返回410", gbStream.getApp(), gbStream.getStream());
                        responseAck(evt, Response.GONE, "media server not found");
                        return;
                    }
                    Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, gbStream.getApp(), gbStream.getStream());
                    if (!streamReady) {
                        logger.info("[ app={}, stream={} ]通道离线，返回400", gbStream.getApp(), gbStream.getStream());
                        responseAck(evt, Response.BAD_REQUEST, "channel [" + gbStream.getGbId() + "] offline");
                        return;
                    }
                    responseAck(evt, Response.CALL_IS_BEING_FORWARDED); // 通道存在，发181，呼叫转接中
                } else {
                    logger.info("通道不存在，返回404");
                    responseAck(evt, Response.NOT_FOUND); // 通道不存在，发404，资源不存在
                    return;
                }
                // 解析sdp消息, 使用jainsip 自带的sdp解析方式
                String contentString = new String(request.getRawContent());

                // jainSip不支持y=字段， 移除移除以解析。
                int ssrcIndex = contentString.indexOf("y=");
                //ssrc规定长度为10字节，不取余下长度以避免后续还有“f=”字段
                String ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
                String substring = contentString.substring(0, contentString.indexOf("y="));
                SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);

                //  获取支持的格式
                Vector mediaDescriptions = sdp.getMediaDescriptions(true);
                // 查看是否支持PS 负载96
                //String ip = null;
                int port = -1;
                //boolean recvonly = false;
                boolean mediaTransmissionTCP = false;
                Boolean tcpActive = null;
                for (Object description : mediaDescriptions) {
                    MediaDescription mediaDescription = (MediaDescription) description;
                    Media media = mediaDescription.getMedia();

                    Vector mediaFormats = media.getMediaFormats(false);
                    if (mediaFormats.contains("96")) {
                        port = media.getMediaPort();
                        //String mediaType = media.getMediaType();
                        String protocol = media.getProtocol();

                        // 区分TCP发流还是udp， 当前默认udp
                        if ("TCP/RTP/AVP".equals(protocol)) {
                            String setup = mediaDescription.getAttribute("setup");
                            if (setup != null) {
                                mediaTransmissionTCP = true;
                                if ("active".equals(setup)) {
                                    tcpActive = true;
                                } else if ("passive".equals(setup)) {
                                    tcpActive = false;
                                }
                            }
                        }
                        break;
                    }
                }
                if (port == -1) {
                    logger.info("不支持的媒体格式，返回415");
                    // 回复不支持的格式
                    responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式，发415
                    return;
                }
                String username = sdp.getOrigin().getUsername();
                String addressStr = sdp.getOrigin().getAddress();
                //String sessionName = sdp.getSessionName().getValue();
                logger.info("[上级点播]用户：{}， 地址：{}:{}， ssrc：{}", username, addressStr, port, ssrc);
                Device device = null;
                // 通过 channel 和 gbStream 是否为null 值判断来源是直播流合适国标
                if (channel != null) {
                    device = storager.queryVideoDeviceByPlatformIdAndChannelId(requesterId, channelId);
                    if (device == null) {
                        logger.warn("点播平台{}的通道{}时未找到设备信息", requesterId, channel);
                        responseAck(evt, Response.SERVER_INTERNAL_ERROR);
                        return;
                    }
                    mediaServerItem = playService.getNewMediaServerItem(device);
                    if (mediaServerItem == null) {
                        logger.warn("未找到可用的zlm");
                        responseAck(evt, Response.BUSY_HERE);
                        return;
                    }
                    SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
                            device.getDeviceId(), channelId,
                            mediaTransmissionTCP);
                    if (tcpActive != null) {
                        sendRtpItem.setTcpActive(tcpActive);
                    }
                    if (sendRtpItem == null) {
                        logger.warn("服务器端口资源不足");
                        responseAck(evt, Response.BUSY_HERE);
                        return;
                    }

                    // 写入redis， 超时时回复
                    redisCatchStorage.updateSendRTPSever(sendRtpItem);
                    // 通知下级推流，
                    PlayResult playResult = playService.play(mediaServerItem, device.getDeviceId(), channelId, (mediaServerItemInUSe, responseJSON) -> {
                        // 收到推流， 回复200OK, 等待ack
                        // if (sendRtpItem == null) return;
                        sendRtpItem.setStatus(1);
                        redisCatchStorage.updateSendRTPSever(sendRtpItem);
                        // TODO 添加对tcp的支持

                        StringBuffer content = new StringBuffer(200);
                        content.append("v=0\r\n");
                        content.append("o=" + "00000" + " 0 0 IN IP4 " + mediaServerItemInUSe.getSdpIp() + "\r\n");
                        content.append("s=Play\r\n");
                        content.append("c=IN IP4 " + mediaServerItemInUSe.getSdpIp() + "\r\n");
                        content.append("t=0 0\r\n");
                        content.append("m=video " + sendRtpItem.getLocalPort() + " RTP/AVP 96\r\n");
                        content.append("a=sendonly\r\n");
                        content.append("a=rtpmap:96 PS/90000\r\n");
                        content.append("y=" + ssrc + "\r\n");
                        content.append("f=\r\n");

                        try {
                            responseAck(evt, content.toString());
                        } catch (SipException e) {
                            e.printStackTrace();
                        } catch (InvalidArgumentException e) {
                            e.printStackTrace();
                        } catch (ParseException e) {
                            e.printStackTrace();
                        }
                    }, ((event) -> {
                        // 未知错误。直接转发设备点播的错误
                        Response response = null;
                        try {
                            response = getMessageFactory().createResponse(event.getResponse().getStatusCode(), evt.getRequest());
                            ServerTransaction serverTransaction = getServerTransaction(evt);
                            serverTransaction.sendResponse(response);
                            if (serverTransaction.getDialog() != null) serverTransaction.getDialog().delete();
                        } catch (ParseException | SipException | InvalidArgumentException e) {
                            e.printStackTrace();
                        }
                    }));
                    if (logger.isDebugEnabled()) {
                        logger.debug(playResult.getResult().toString());
                    }

                } else if (gbStream != null) {
                    SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, addressStr, port, ssrc, requesterId,
                            gbStream.getApp(), gbStream.getStream(), channelId,
                            mediaTransmissionTCP);

                    if (tcpActive != null) {
                        sendRtpItem.setTcpActive(tcpActive);
                    }
                    if (sendRtpItem == null) {
                        logger.warn("服务器端口资源不足");
                        responseAck(evt, Response.BUSY_HERE);
                        return;
                    }

                    // 写入redis， 超时时回复
                    redisCatchStorage.updateSendRTPSever(sendRtpItem);

                    sendRtpItem.setStatus(1);
                    redisCatchStorage.updateSendRTPSever(sendRtpItem);
                    // TODO 添加对tcp的支持
                    StringBuffer content = new StringBuffer(200);
                    content.append("v=0\r\n");
                    content.append("o=" + "00000" + " 0 0 IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
                    content.append("s=Play\r\n");
                    content.append("c=IN IP4 " + mediaServerItem.getSdpIp() + "\r\n");
                    content.append("t=0 0\r\n");
                    content.append("m=video " + sendRtpItem.getLocalPort() + " RTP/AVP 96\r\n");
                    content.append("a=sendonly\r\n");
                    content.append("a=rtpmap:96 PS/90000\r\n");
                    content.append("y=" + ssrc + "\r\n");
                    content.append("f=\r\n");

                    try {
                        responseAck(evt, content.toString());
                    } catch (SipException e) {
                        e.printStackTrace();
                    } catch (InvalidArgumentException e) {
                        e.printStackTrace();
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }

            } else {
                // 非上级平台请求，查询是否设备请求（通常为接收语音广播的设备）
                Device device = storager.queryVideoDevice(requesterId);
                if (device != null) {
                    logger.info("收到设备" + requesterId + "的语音广播Invite请求");
                    responseAck(evt, Response.TRYING);

                    String contentString = new String(request.getRawContent());
                    // jainSip不支持y=字段， 移除移除以解析。
                    String substring = contentString;
                    String ssrc = "0000000404";
                    int ssrcIndex = contentString.indexOf("y=");
                    if (ssrcIndex > 0) {
                        substring = contentString.substring(0, ssrcIndex);
                        ssrc = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
                    }
                    ssrcIndex = substring.indexOf("f=");
                    String fvalue = "v/////a/1/8/1";
                    if (ssrcIndex > 0) {
                        substring = contentString.substring(0, ssrcIndex);
                        String tempValue = contentString.substring(ssrcIndex);
                        fvalue = tempValue.split("=")[1];
                    }
                    SessionDescription sdp = SdpFactory.getInstance().createSessionDescription(substring);

                    //  获取支持的格式
                    Vector mediaDescriptions = sdp.getMediaDescriptions(true);
                    // 查看是否支持PS 负载96
                    int port = -1;
                    //boolean recvonly = false;
                    boolean mediaTransmissionTCP = false;
                    Boolean tcpActive = null;
                    for (int i = 0; i < mediaDescriptions.size(); i++) {
                        MediaDescription mediaDescription = (MediaDescription) mediaDescriptions.get(i);
                        Media media = mediaDescription.getMedia();

                        Vector mediaFormats = media.getMediaFormats(false);
                        if (mediaFormats.contains("8")) {
                            port = media.getMediaPort();
                            String protocol = media.getProtocol();
                            // 区分TCP发流还是udp， 当前默认udp
                            if ("TCP/RTP/AVP".equals(protocol)) {
                                String setup = mediaDescription.getAttribute("setup");
                                if (setup != null) {
                                    mediaTransmissionTCP = true;
                                    if ("active".equals(setup)) {
                                        tcpActive = true;
                                    } else if ("passive".equals(setup)) {
                                        tcpActive = false;
                                    }
                                }
                            }
                            break;
                        }
                    }
                    if (port == -1) {
                        logger.info("不支持的媒体格式，返回415");
                        // 回复不支持的格式
                        responseAck(evt, Response.UNSUPPORTED_MEDIA_TYPE); // 不支持的格式，发415
                        return;
                    }
                    String username = sdp.getOrigin().getUsername();
                    String addressStr = sdp.getOrigin().getAddress();


//					// 写入redis， 超时时回复
//					redisCatchStorage.updateSendRTPSever(sendRtpItem);
//
//					sendRtpItem.setStatus(1);
//					redisCatchStorage.updateSendRTPSever(sendRtpItem);


                    try {
                        String ipAndPort = getIpAndPort(device.getDeviceId(), sipURI.getHost());
                        int appPort = Integer.parseInt(ipAndPort.split(",")[1]);
                        int ipcPort = appPort + 10000;
                        StringBuffer content = (new StringBuffer("v=0\r\n")).append("o=34020000002000000001 0 0 IN IP4 " + sipURI.getHost() + "\r\n").append("s=Play\r\n").append("c=IN IP4 " + sipURI.getHost() + "\r\n").append("t=0 0\r\n").append("m=audio " + ipcPort + " TCP/RTP/AVP 8\r\n").append("a=rtpmap:8 PCMA/8000\r\n").append("a=setup:passive\r\n").append("a=sendonly\r\n").append("y=" + ssrc + "\r\n").append("f=" + fvalue + "\r\n");
                        System.out.println("appPort-------------------------" + appPort);
                        System.out.println("ipcPort-------------------------" + ipcPort);
                        if ("NOSTART".equalsIgnoreCase(ipAndPort.split(",")[2])) {
                            TcpTrans tcpTrans = new TcpTrans(appPort, ipcPort,device.getDeviceId(),redisUtil );
                            tcpTrans.start();
                        }
                        responseAck(evt, content.toString());
                    } catch (SipException e) {
                        e.printStackTrace();
                    } catch (InvalidArgumentException e) {
                        e.printStackTrace();
                    } catch (ParseException e) {
                        e.printStackTrace();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    logger.info("设备{}请求语音流，地址：{}:{}，ssrc：{}", username, addressStr, port, ssrc);

                } else {
                    logger.warn("来自无效设备/平台的请求"+requesterId);
                    responseAck(evt, Response.BAD_REQUEST);
                }
            }

        } catch (SipException | InvalidArgumentException | ParseException e) {
            e.printStackTrace();
            logger.warn("sdp解析错误");
            e.printStackTrace();
        } catch (SdpParseException e) {
            e.printStackTrace();
        } catch (SdpException e) {
            e.printStackTrace();
        }
    }

    /***
     * 回复200 OK
     * @param evt
     * @throws SipException
     * @throws InvalidArgumentException
     * @throws ParseException
     */
    private void responseAck(RequestEvent evt) throws SipException, InvalidArgumentException, ParseException {
        Response response = getMessageFactory().createResponse(Response.OK, evt.getRequest());
        String content0 = evt.getRequest().toString().replaceAll("(\r\n|\n)", "<br/>");
        String content1[] = content0.split("<br/>");
        HashMap<String, String> mapContent = new HashMap<>();
        for (int i = 0; i < content1.length; i++) {
            if (content1[i].length() > 2) {
                if ("=".equals(content1[i].substring(1, 2))) {
                    String content2[] = content1[i].split("=");
                    mapContent.put(content2[0], content2[1]);
                } else {
                    if (content1[i].startsWith("From")) {
                        String content2[] = content1[i].split(":");
                        mapContent.put("From", content2[2]);
                    } else if (content1[i].startsWith("To")) {
                        String content2[] = content1[i].split(":");
                        mapContent.put("To", content2[2]);
                    }
                }
            } else {
                continue;
            }
        }
        String to[] = mapContent.get("To").split("@");
        //保存port到redis

        String m = mapContent.get("m").substring(6, 11);
        port1 = m;
//		RedisTemplate redisTemplate=new RedisTemplate();
//		redisTemplate.opsForValue().set("port1",m);
        // 添加Contact头
        Address concatAddress = SipFactory.getInstance().createAddressFactory().
                createAddress(SipFactory.getInstance().createAddressFactory().
                        createSipURI(to[0], to[1] + ":" + "5060"));
        response.addHeader(SipFactory.getInstance().createHeaderFactory().createContactHeader(concatAddress));
        response.addHeader(evt.getRequest().getHeader(ContentType.NAME));
        String toIp = "192.168.0.99";// sipConfig.getIp();
        //toIp = to[1];
        //response.addHeader(evt.getRequest().getHeader(UserAgent.NAME));
        ContentTypeHeader contentTypeHeader = SipFactory.getInstance().createHeaderFactory().createContentTypeHeader("application", "SDP");

        StringBuffer contentEnd = new StringBuffer("v=0\r\n")
                .append("o=" + to[0] + " 0 0 IN IP4 " + toIp + "\r\n")
                .append("s=Play\r\n")
                .append("c=IN IP4 " + toIp + "\r\n")
                .append("t=0 0\r\n")
//				.append("m=audio 8000 RTP/AVP 8\r\n")
                .append("m=audio 8000 TCP/RTP/AVP 8\r\n")
                .append("a=rtpmap:8 PCMA/8000\r\n")
                .append("a=setup:passive\r\n")
                .append("a=sendonly\r\n")
                .append("y=" + mapContent.get("y") + "\r\n")
                .append("f=" + mapContent.get("f").replaceAll("0", "") + "\r\n");
        logger.info("content返回结果:" + contentEnd);
        response.setContent(contentEnd, contentTypeHeader);
        getServerTransaction(evt).sendResponse(response);
    }


    /***
     * 回复状态码
     * 100 trying
     * 200 OK
     * 400
     * 404
     * @param evt
     * @throws SipException
     * @throws InvalidArgumentException
     * @throws ParseException
     */
    private void responseAck(RequestEvent evt, int statusCode) throws SipException, InvalidArgumentException, ParseException {
        Response response = getMessageFactory().createResponse(statusCode, evt.getRequest());
        ServerTransaction serverTransaction = getServerTransaction(evt);
        serverTransaction.sendResponse(response);
        if (statusCode >= 200) {
            if (serverTransaction.getDialog() != null) serverTransaction.getDialog().delete();
        }
    }

    private void responseAck(RequestEvent evt, int statusCode, String msg) throws SipException, InvalidArgumentException, ParseException {
        Response response = getMessageFactory().createResponse(statusCode, evt.getRequest());
        response.setReasonPhrase(msg);
        ServerTransaction serverTransaction = getServerTransaction(evt);
        serverTransaction.sendResponse(response);
        if (statusCode >= 200) {
            if (serverTransaction.getDialog() != null) serverTransaction.getDialog().delete();
        }
    }

    /**
     * 回复带sdp的200
     *
     * @param evt
     * @param sdp
     * @throws SipException
     * @throws InvalidArgumentException
     * @throws ParseException
     */
    private void responseAck(RequestEvent evt, String sdp) throws SipException, InvalidArgumentException, ParseException {
        Response response = getMessageFactory().createResponse(Response.OK, evt.getRequest());
        SipFactory sipFactory = SipFactory.getInstance();
        ContentTypeHeader contentTypeHeader = sipFactory.createHeaderFactory().createContentTypeHeader("APPLICATION", "SDP");
        response.setContent(sdp, contentTypeHeader);

        SipURI sipURI = (SipURI) evt.getRequest().getRequestURI();

        Address concatAddress = sipFactory.createAddressFactory().createAddress(
                sipFactory.createAddressFactory().createSipURI(sipURI.getUser(), sipURI.getHost() + ":" + sipURI.getPort()
                ));
        response.addHeader(sipFactory.createHeaderFactory().createContactHeader(concatAddress));
        getServerTransaction(evt).sendResponse(response);
    }


    public SIPCommanderFroPlatform getCmderFroPlatform() {
        return cmderFroPlatform;
    }

    public void setCmderFroPlatform(SIPCommanderFroPlatform cmderFroPlatform) {
        this.cmderFroPlatform = cmderFroPlatform;
    }

    public IVideoManagerStorager getStorager() {
        return storager;
    }

    public void setStorager(IVideoManagerStorager storager) {
        this.storager = storager;
    }

    public SIPCommander getCmder() {
        return cmder;
    }

    public void setCmder(SIPCommander cmder) {
        this.cmder = cmder;
    }

    public IPlayService getPlayService() {
        return playService;
    }

    public void setPlayService(IPlayService playService) {
        this.playService = playService;
    }

    public IRedisCatchStorage getRedisCatchStorage() {
        return redisCatchStorage;
    }

    public void setRedisCatchStorage(IRedisCatchStorage redisCatchStorage) {
        this.redisCatchStorage = redisCatchStorage;
    }

    public IMediaServerService getMediaServerService() {
        return mediaServerService;
    }

    public void setMediaServerService(IMediaServerService mediaServerService) {
        this.mediaServerService = mediaServerService;
    }

    public void setRedisUtil(RedisUtil redisUtil) {
        this.redisUtil = redisUtil;
    }

    private String getIpAndPort(String deviceId, String host) {
        String ipAndPort;
        String appPortKey = "APPPORT_";
        if (redisUtil.hasKey(appPortKey + deviceId)) {
            ipAndPort = redisUtil.get(appPortKey + deviceId).toString() + ",STARTED";
        } else {
            Object appPort = redisUtil.leftPop(appPortKey + "LIST");
            ipAndPort = host + "," + appPort;
            redisUtil.set(appPortKey + deviceId, ipAndPort);
            ipAndPort = ipAndPort + ",NOSTART";
        }
        return ipAndPort;
    }
}
